package cn.chenli.job.tool.engine.java;

import cn.chenli.job.tool.engine.common.ExecuteResult;
import cn.chenli.job.tool.engine.common.ListenerSupportEngine;
import cn.chenli.job.tool.engine.common.ScriptContext;
import cn.chenli.job.tool.engine.common.ToolEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pro.tools.data.text.ToolStr;

import javax.script.ScriptException;
import javax.tools.*;
import java.io.File;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * java处理脚本
 * <p>
 * Created by 陈李 on 2017.08.10
 */
public class JavaEngine extends ListenerSupportEngine {
    protected Logger logger = LoggerFactory.getLogger(this.getClass());
    private String savePath = null;
    private String classpath = "";
    private Map<String, JavaCodeContext> cache = new ConcurrentHashMap<>();
    private URL[] loaderUrl;

    private static List<String> list = new ArrayList<>();

    public JavaEngine() throws Exception {
        // 1.Java输入输出临时路径,2.拼接保存路径
        savePath = System.getProperty("java.io.tmpdir").concat("/org/hsweb/java/engine/");
        //创建文件夹
        new File(savePath + "src").mkdirs();
        //创建文件夹
        new File(savePath + "bin").mkdirs();

        String classPath = this.getClass().getResource("/").getPath();
        File file = new File(new File(classPath).getParent());
        list.add(classPath);
        getJarPath(file);
        StringBuffer sb = new StringBuffer();
        list.forEach(str ->{
            sb.append(str);
            sb.append(";");
        });
        classpath = sb.toString();
        loaderUrl = new URL[]{new File(savePath + "bin").toURI().toURL()};
    }

    @Override
    public void init(String... contents) throws Exception {

    }

    @Override
    public boolean compile(String id, String code) throws Exception {
        String name = ToolEngine.getClassName(code);//获取类名
        String packageName = ToolEngine.getPackage(code);//获取包名
        if (!ToolStr.isBlank(packageName)) {
            name = packageName + "." + name;//完整的包名+类名
        }
        try {
            //企图覆盖非动态编译的类
            Class.forName(name);//若该方法没有抛出ClassNotFoundException没找到异常，则说明该类存在则抛出异常
            throw new UnsupportedOperationException("class " + name + " is exists!");
        } catch (ClassNotFoundException e) {

        }

        String fileName = savePath + "src/" + name.replace('.', '/') + ".java";
        File file = new File(fileName);
        if (file.exists()) file.delete();
        //输出java文件
        ToolEngine.writeString2File(code, fileName, "utf-8");
        //创建调用JVM的编译器来编译java源文件工具类
        JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
        //创建诊断信息监听器, 用于手机诊断信息
        DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();

        List<JavaFileObject> javaFileObjects = new ArrayList<>();
        //获取FileManager
        StandardJavaFileManager fm = compiler.getStandardFileManager(null, Locale.CHINA, Charset.forName("UTF-8"));
        javaFileObjects.add(new CharSequenceJavaFileObject(savePath, name, code));
        List<String> options = new ArrayList<>();
        options.add("-d");//参数-d指定字节码输出目录
        options.add(savePath + "bin");
        options.add("-cp");
        options.add(classpath);
        //动态编译方式，一种compiler直接调用run方法,也可以创建CompilationTask对象调用call方法编译
        //使用一个类的源码字符串构造一个JavaFileObject, 让后调用编译API编译此JavaFileObject对象, 并生成字节码, 最后使用放射调用编译结果
        JavaCompiler.CompilationTask task = compiler.getTask(null, fm, diagnostics, options, null, javaFileObjects);
        boolean success = task.call();
        if (success) {
            //使用URLClassLoader加载class到内存,DynamicClassLoader继承至URLClassLoader
            DynamicClassLoader dynamicClassLoader = new DynamicClassLoader(loaderUrl, JavaEngine.class.getClassLoader());
            Class<?> clazz = dynamicClassLoader.loadClass(name);
            Executor executor = null;
            if (ToolEngine.instanceOf(clazz, Executor.class)) {
                executor = (Executor) clazz.newInstance();
            }
            JavaCodeContext context = new JavaCodeContext(id, ToolEngine.defaultEncode(code), clazz, executor);
            cache.put(id, context);
            return clazz != null;
        } else {
            StringBuilder builder = new StringBuilder();
            //输出诊断信息
            for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
                //可以在此处自定义编译诊(错误)断信息的输出格式
                builder.append(diagnostic).append("\n");
            }
            throw new ScriptException(builder.toString());
        }
    }

    @Override
    public ExecuteResult execute(String id) {
        return execute(id, new HashMap<>());
    }

    @Override
    public ExecuteResult execute(String id, Map<String, Object> param) {
        long startTime = System.currentTimeMillis();
        ExecuteResult result = new ExecuteResult();
        JavaCodeContext context = cache.get(id);
        try {
            if (context != null) {
                doListenerBefore(context);
                Executor executor = context.getExecutor();
                Class clazz = context.getCodeClass();
                if (executor != null) {
                    result.setResult(executor.execute());
                    result.setSuccess(true);
                } else {
                    result.setSuccess(true);
                    result.setResult(clazz);
                }
            } else {
                result.setSuccess(false);
                result.setResult(null);
                result.setMessage(String.format("class(%s): %s not found!", id, "java"));
            }
        } catch (Exception e) {
            result.setException(e);
        }
        result.setUseTime(System.currentTimeMillis() - startTime);
        doListenerAfter(context, result);
        return result;
    }

    @Override
    public boolean remove(String id) {
        return cache.remove(id) != null;
    }

    @Override
    public boolean compiled(String id) {
        return cache.containsKey(id);
    }

    @Override
    public ScriptContext getContext(String id) {
        return cache.get(id);
    }

    protected class JavaCodeContext extends ScriptContext {
        private Class codeClass;
        private Executor executor;

        public JavaCodeContext(String id, String md5, Class codeClass, Executor executor) {
            super(id, md5);
            this.codeClass = codeClass;
            this.executor = executor;
        }

        public Class getCodeClass() {
            return codeClass;
        }

        public Executor getExecutor() {
            return executor;
        }
    }

    /**
     * 获取jia包
     * @param file：文件
     */
    static void getJarPath(File file){
        if(file == null){
            return;
        }
        File[] files = file.listFiles();
        if(files == null){
            return;
        }
        for(File f : files){
            if(f.isFile()){
                String fileName = f.getName();
                int last = fileName.lastIndexOf(".");
                if(last == -1){
                    continue;
                }
                if("jar".equals(fileName.substring(last+1))){
                    list.add(f.getPath());
                }
            }
            if(f.isDirectory()){
                getJarPath(f);
            }
        }
    }
}
